//===--- IAMInference.cpp - Import as member inference system -------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file implements support for inferring when globals can be imported as
// members
//
//===----------------------------------------------------------------------===//
#include "polarphp/clangimporter/internal/CFTypeInfo.h"
#include "polarphp/clangimporter/internal/IAMInference.h"
#include "polarphp/clangimporter/internal/ImporterImpl.h"

#include "polarphp/ast/AstContext.h"
#include "polarphp/basic/StringExtras.h"

#include "clang/AST/AstContext.h"
#include "clang/AST/Decl.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Sema/Sema.h"
#include "clang/Sema/Lookup.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/Support/Debug.h"

#include <array>

#define DEBUG_TYPE "Infer import as member"

// Statistics for failure to infer
STATISTIC(FailInferVar, "# of variables unable to infer");
STATISTIC(FailInferFunction, "# of functions unable to infer");

// Specifically skipped/avoided
STATISTIC(SkipLeadingUnderscore,
          "# of globals skipped due to leading underscore");
STATISTIC(SkipCFMemoryManagement,
          "# of CF memory management globals skipped");

// Success statistics
STATISTIC(SuccessImportAsTypeID, "# imported as 'typeID'");
STATISTIC(SuccessImportAsConstructor, "# imported as 'init'");
STATISTIC(SuccessImportAsInstanceComputedProperty,
          "# imported as instance computed property");
STATISTIC(SuccessImportAsStaticProperty,
          "# imported as static (stored) property");
STATISTIC(SuccessImportAsStaticComputedProperty,
          "# imported as static computed property");
STATISTIC(SuccessImportAsStaticMethod, "# imported as static method");
STATISTIC(SuccessImportAsInstanceMethod, "# imported as instance method");

// Statistics for why we couldn't infer in more specific ways, and fell back to
// methods
STATISTIC(InvalidPropertyStaticNumParams,
          "couldn't infer as static property: invalid number of parameters");
STATISTIC(InvalidPropertyInstanceNumParams,
          "couldn't infer as instance property: invalid number of parameters");
STATISTIC(InvalidPropertyStaticGetterSetterType,
          "couldn't infer as static property: getter/setter type mismatch");
STATISTIC(InvalidPropertyInstanceGetterSetterType,
          "couldn't infer as instance property: getter/setter type mismatch");
STATISTIC(InvalidPropertyInstanceNoSelf,
          "couldn't infer as instance property: couldn't find self");

// Omit needless words stats
STATISTIC(OmitNumTimes,
          "# of times omitNeedlessWords was able to fire on an API");

using namespace polar;
using namespace importer;

using NameBuffer = SmallString<32>;

static const std::array<StringRef, 2> InitSpecifiers{{
                                                        StringRef("Create"), StringRef("Make"),
                                                     }};
static const std::array<StringRef, 2> PropertySpecifiers{{
                                                            StringRef("Get"), StringRef("Set"),
                                                         }};

IAMOptions IAMOptions::getDefault() { return {}; }

// As append, but skip a repeated word at the boundary. First-letter-case
// insensitive.
// Example: appendUniq("FooBar", "barBaz") ==> "FooBarBaz"
static void appendUniq(NameBuffer &src, StringRef toAppend) {
   if (src.empty()) {
      src = toAppend;
      return;
   }

   auto appendWords = camel_case::getWords(toAppend);
   StringRef lastWord = *camel_case::getWords(src).rbegin();
   auto wI = appendWords.begin();
   while (wI != appendWords.end() && wI->equals_lower(lastWord))
      ++wI;
   src.append(wI.getRestOfStr());
}

static StringRef skipLeadingUnderscores(StringRef str) {
   while (!str.empty() && str.startswith("_"))
      str = str.drop_front(1);
   return str;
}

// Form a humble camel name from a string. Skips leading underscores.
static void formHumbleCamelName(StringRef str, NameBuffer &out) {
   str = skipLeadingUnderscores(str);
   auto newStr = camel_case::toLowercaseInitialisms(str, out);
   if (newStr == str)
      out = newStr;
}

// Form a humble camel by appending the two strings and adjusting case as
// needed. Skips leading underscores in either name, and skips a repeated word
// at the boundary. Example: formHumbleCamelName("__FooBar", "barBaz") ==>
// "fooBarBaz".
static void formHumbleCamelName(StringRef left, StringRef right,
                                NameBuffer &out) {
   left = skipLeadingUnderscores(left);
   if (left == "") {
      formHumbleCamelName(right, out);
      return;
   }
   right = skipLeadingUnderscores(right);
   if (right == "") {
      formHumbleCamelName(left, out);
      return;
   }

   StringRef lastWord = *camel_case::getWords(left).rbegin();
   auto rightWords = camel_case::getWords(right);
   auto wI = rightWords.begin();
   while (wI != rightWords.end() && wI->equals_lower(lastWord))
      ++wI;

   formHumbleCamelName(left, out);
   camel_case::appendSentenceCase(out, wI.getRestOfStr());
}

static bool hasWord(StringRef s, StringRef matchWord) {
   for (auto word : camel_case::getWords(s))
      if (word == matchWord)
         return true;
   return false;
}

// Drops the specified word, and returns the number of times it was dropped.
// When forming the resultant string, will call appendUniq to skip repeated
// words at the boundary.
static unsigned dropWordUniq(StringRef str, StringRef word, NameBuffer &out) {
   unsigned numDropped = 0;
   auto words = camel_case::getWords(str);
   for (auto wI = words.begin(), wE = words.end(); wI != wE; ++wI)
      if (*wI == word)
         ++numDropped;
      else
         appendUniq(out, *wI);

   return numDropped;
}

static clang::Module *getSubmodule(const clang::NamedDecl *decl, clang::Sema &clangSema) {
   if (auto m = decl->getImportedOwningModule())
      return m;
   if (auto m = decl->getLocalOwningModule())
      return m;
   if (auto m = clangSema.getPreprocessor().getCurrentModule())
      return m;
   if (auto m = clangSema.getPreprocessor().getCurrentLexerSubmodule())
      return m;

   return nullptr;
}

static clang::Module *getTopModule(clang::Module *m) {
   while (m->Parent)
      m = m->Parent;
   return m;
}
static clang::Module *getTopModule(const clang::NamedDecl *decl, clang::Sema &clangSema) {
   auto m = getSubmodule(decl, clangSema);
   if (!m)
      return nullptr;
   return getTopModule(m);
}


namespace {
class IAMInference {
   AstContext &context;
   clang::Sema &clangSema;
   IAMOptions options;

public:
   IAMInference(AstContext &ctx, clang::Sema &sema, IAMOptions opts)
      : context(ctx), clangSema(sema), options(opts) {
      (void)options;
   }

   IAMResult infer(const clang::NamedDecl *);
   IAMResult inferVar(const clang::VarDecl *);

private:
   // typeID
   IAMResult importAsTypeID(const clang::QualType typeIDTy,
                            EffectiveClangContext effectiveDC) {
      ++SuccessImportAsTypeID;
      return {formDeclName("typeID", /*isInitializer=*/false),
              IAMAccessorKind::Getter, effectiveDC};
   }

   // Init
   IAMResult importAsConstructor(StringRef name, StringRef initSpecifier,
                                 ArrayRef<const clang::ParmVarDecl *> params,
                                 EffectiveClangContext effectiveDC) {
      ++SuccessImportAsConstructor;
      NameBuffer buf;
      StringRef prefix = buf;
      if (name != initSpecifier) {
         assert(name.size() > initSpecifier.size() &&
                "should have more words in it");
         bool didDrop = dropWordUniq(name, initSpecifier, buf);
         (void)didDrop;
         prefix = buf;

         // Skip "with"
         auto prefixWords = camel_case::getWords(prefix);
         if (prefixWords.begin() != prefixWords.end() &&
             (*prefixWords.begin() == "With" || *prefixWords.begin() == "with")) {
            prefix = prefix.drop_front(4);
         }

         // Skip "CF" or "NS"
         prefixWords = camel_case::getWords(prefix);
         if (prefixWords.begin() != prefixWords.end() &&
             (*prefixWords.begin() == "CF" || *prefixWords.begin() == "NS")) {
            prefix = prefix.drop_front(2);
         }

         assert(didDrop != 0 && "specifier not present?");
      }
      return {formDeclName("init", true, params, prefix), effectiveDC};
   }

   // Instance computed property
   IAMResult
   importAsInstanceProperty(StringRef name, StringRef propSpec, unsigned selfIdx,
                            ArrayRef<const clang::ParmVarDecl *> nonSelfParams,
                            const clang::FunctionDecl *pairedAccessor,
                            EffectiveClangContext effectiveDC) {
      ++SuccessImportAsInstanceComputedProperty;
      IAMAccessorKind kind =
         propSpec == "Get" ? IAMAccessorKind::Getter : IAMAccessorKind::Setter;
      assert(kind == IAMAccessorKind::Getter || pairedAccessor && "no set-only");

      return {formDeclName(name, /*isInitializer=*/false),
              kind, selfIdx, effectiveDC};
   }

   // Instance method
   IAMResult
   importAsInstanceMethod(StringRef name, unsigned selfIdx,
                          ArrayRef<const clang::ParmVarDecl *> nonSelfParams,
                          EffectiveClangContext effectiveDC) {
      ++SuccessImportAsInstanceMethod;
      return {formDeclName(name, /*isInitializer=*/false, nonSelfParams),
              selfIdx, effectiveDC};
   }

   // Static stored property
   IAMResult importAsStaticProperty(StringRef name,
                                    EffectiveClangContext effectiveDC) {
      ++SuccessImportAsStaticProperty;
      return {formDeclName(name, /*isInitializer=*/false), effectiveDC};
   }

   // Static computed property
   IAMResult
   importAsStaticProperty(StringRef name, StringRef propSpec,
                          ArrayRef<const clang::ParmVarDecl *> nonSelfParams,
                          const clang::FunctionDecl *pairedAccessor,
                          EffectiveClangContext effectiveDC) {
      ++SuccessImportAsStaticComputedProperty;
      IAMAccessorKind kind =
         propSpec == "Get" ? IAMAccessorKind::Getter : IAMAccessorKind::Setter;
      assert(kind == IAMAccessorKind::Getter || pairedAccessor && "no set-only");

      return {formDeclName(name, /*isInitializer=*/false),
              kind, effectiveDC};
   }

   // Static method
   IAMResult
   importAsStaticMethod(StringRef name,
                        ArrayRef<const clang::ParmVarDecl *> nonSelfParams,
                        EffectiveClangContext effectiveDC) {
      ++SuccessImportAsStaticMethod;
      return {formDeclName(name, /*isInitializer=*/false, nonSelfParams),
              effectiveDC};
   }

   Identifier getIdentifier(StringRef str) {
      if (str == "")
         return Identifier();
      return context.getIdentifier(str);
   }

   template <typename DeclType>
   inline DeclType *clangLookup(StringRef name,
                                clang::Sema::LookupNameKind kind);

   clang::TypeDecl *clangLookupTypeDecl(StringRef name) {
      if (auto ty = clangLookup<clang::TypedefNameDecl>(
         name, clang::Sema::LookupNameKind::LookupOrdinaryName))
         return ty;

      return clangLookup<clang::TagDecl>(
         name, clang::Sema::LookupNameKind::LookupTagName);
   }

   clang::FunctionDecl *clangLookupFunction(StringRef name) {
      return clangLookup<clang::FunctionDecl>(
         name, clang::Sema::LookupNameKind::LookupOrdinaryName);
   }

   EffectiveClangContext findTypeAndMatch(StringRef workingName,
                                          NameBuffer &outStr) {
      // FIXME: drop mutable...

      // TODO: should we try some form of fuzzy or fuzzier matching?

      // Longest-prefix matching, alternate with checking for a trailing "Ref"
      // suffix and the prefix itself. We iterate from the back to the beginning.
      auto words = camel_case::getWords(workingName);
      for (auto rWordsIter = words.rbegin(), rWordsEnd = words.rend();
           rWordsIter != rWordsEnd; ++rWordsIter) {
         NameBuffer nameAttempt;
         nameAttempt.append(rWordsIter.base().getPriorStr());
         StringRef prefix = nameAttempt;
         nameAttempt.append("Ref");
         StringRef prefixWithRef = nameAttempt;

         if (auto tyDecl = clangLookupTypeDecl(prefixWithRef)) {
            outStr.append(workingName.drop_front(prefix.size()));
            return getEffectiveDC(clang::QualType(tyDecl->getTypeForDecl(), 0));
         }
         if (auto tyDecl = clangLookupTypeDecl(prefix)) {
            outStr.append(workingName.drop_front(prefix.size()));
            return getEffectiveDC(clang::QualType(tyDecl->getTypeForDecl(), 0));
         }
      }

      return {};
   }

   bool validToImportAsProperty(const clang::FunctionDecl *originalDecl,
                                StringRef propSpec, Optional<unsigned> selfIndex,
                                const clang::FunctionDecl *&pairedAccessor);

   const clang::FunctionDecl *findPairedAccessor(StringRef name,
                                                 StringRef propSpec) {
      NameBuffer pairName;
      auto words = camel_case::getWords(name);
      for (auto word : words) {
         if (word == propSpec) {
            if (propSpec == "Get") {
               pairName.append("Set");
            } else {
               assert(propSpec == "Set");
               pairName.append("Get");
            }
         } else {
            pairName.append(word);
         }
      }

      return clangLookupFunction(pairName);
   }

   DeclBaseName getHumbleBaseName(StringRef name, bool isInitializer) {
      // Lower-camel-case the incoming name
      NameBuffer buf;
      formHumbleCamelName(name, buf);
      if (isInitializer && buf == "init")
         return DeclBaseName::createConstructor();
      return getIdentifier(buf);
   }

   DeclName formDeclName(StringRef baseName, bool isInitializer) {
      return {getHumbleBaseName(baseName, isInitializer)};
   }

   DeclName formDeclName(StringRef baseName, bool isInitializer,
                         ArrayRef<const clang::ParmVarDecl *> params,
                         StringRef firstPrefix = "") {

      // TODO: redesign from a SmallString to a StringScratchBuffer design for all
      // of this name mangling, since we have to use one for omit needless words
      // anyways

      if (params.empty() && firstPrefix != "") {
         // We need to form an argument label, despite there being no argument
         NameBuffer paramName;
         formHumbleCamelName(firstPrefix, paramName);
         return {context, getHumbleBaseName(baseName, isInitializer),
                 getIdentifier(paramName)};
      }

      StringScratchSpace scratch;
      SmallVector<StringRef, 8> argStrs;
      for (unsigned i = 0; i < params.size(); ++i) {
         NameBuffer paramName;
         if (i == 0 && firstPrefix != "") {
            formHumbleCamelName(firstPrefix, params[i]->getName(), paramName);
         } else {
            // TODO: strip leading underscores
            formHumbleCamelName(params[i]->getName(), paramName);
         }

         argStrs.push_back(scratch.copyString(paramName));
      }

      DeclName beforeOmit;
      (void)beforeOmit;
      {
         SmallVector<Identifier, 8> argLabels;
         for (auto str : argStrs)
            argLabels.push_back(getIdentifier(str));
         LLVM_DEBUG((beforeOmit = {context,
                                   getHumbleBaseName(baseName, isInitializer),
                                   argLabels}));
      }

      SmallVector<OmissionTypeName, 8> paramTypeNames;
      for (auto param : params) {
         paramTypeNames.push_back(getClangTypeNameForOmission(
            clangSema.getASTContext(), param->getType()));
      }

      auto humbleBaseName = getHumbleBaseName(baseName, isInitializer);
      baseName = humbleBaseName.userFacingName();
      bool didOmit =
         omitNeedlessWords(baseName, argStrs, "", "", "", paramTypeNames, false,
                           false, nullptr, scratch);
      SmallVector<Identifier, 8> argLabels;
      for (auto str : argStrs)
         argLabels.push_back(getIdentifier(str));

      DeclName ret(context,
                   getHumbleBaseName(baseName, isInitializer),
                   argLabels);

      if (didOmit) {
         ++OmitNumTimes;
         LLVM_DEBUG(llvm::dbgs() << "omission detected: " << beforeOmit << " ==> "
                                 << ret << "\n");
      }

      return ret;
   }

   bool matchTypeName(StringRef str, clang::QualType qt, NameBuffer &outStr);
   bool match(StringRef str, StringRef toMatch, NameBuffer &outStr);

   EffectiveClangContext getEffectiveDC(clang::QualType qt) {
      // Read through some attributes
      while (qt.getTypePtrOrNull() && isa<clang::AttributedType>(qt.getTypePtr()))
         qt = qt.getSingleStepDesugaredType(clangSema.getASTContext());

      // Read through typedefs until we get to a CF typedef or a non-typedef-ed
      // type
      while (qt.getTypePtrOrNull() && isa<clang::TypedefType>(qt.getTypePtr())) {
         auto typedefType = cast<clang::TypedefType>(qt.getTypePtr());
         if (auto pointeeInfo = CFPointeeInfo::classifyTypedef(
            typedefType->getDecl()->getCanonicalDecl())) {
            if (pointeeInfo.isRecord() || pointeeInfo.isTypedef())
               return {typedefType->getDecl()->getCanonicalDecl()};
            assert(pointeeInfo.isVoid() && "no other type");
            return {};
         }
         qt = qt.getSingleStepDesugaredType(clangSema.getASTContext());
      }

      auto pointeeQT = qt.getTypePtr()->getPointeeType();
      if (pointeeQT != clang::QualType())
         // Retry on the pointee
         return getEffectiveDC(pointeeQT);

      if (auto tagDecl = qt.getTypePtr()->getAsTagDecl()) {
         auto canon = tagDecl->getCanonicalDecl();
         if (canon->getDefinition())
            return {canon};

         // TODO: Once the importer learns how to import un-defined structs, then
         // we will be able to infer them. Until that point, we have to bail
         // because ImportDecl won't be able to re-map this.
         return {};
      }

      // Failed to find a type we can extend
      return {};
   }
};
} // end anonymous namespace

static StringRef getTypeName(clang::QualType qt) {
   if (auto typedefTy = qt->getAs<clang::TypedefType>()) {
      // Check for a CF type name (drop the "Ref")
      auto cfName = getCFTypeName(typedefTy->getDecl()->getCanonicalDecl());
      if (cfName != StringRef())
         return cfName;
   }

   auto identInfo = qt.getBaseTypeIdentifier();
   if (identInfo)
      return identInfo->getName();

   // Otherwise, no name
   return {};
}

bool IAMInference::matchTypeName(StringRef str, clang::QualType qt,
                                 NameBuffer &outStr) {
   StringRef typeName = getTypeName(qt);
   if (typeName == "")
      return false;

   // Special case: Mutable can appear in both and may screw up word order. Or,
   // Mutable can occur in the type name only. Either way, we want to have the
   // potential of successfully matching the type.
   NameBuffer nonMutableStr;
   NameBuffer nonMutableTypeName;
   if (hasWord(typeName, "Mutable")) {
      dropWordUniq(typeName, "Mutable", nonMutableTypeName);
      typeName = nonMutableTypeName;
      if (hasWord(str, "Mutable")) {
         dropWordUniq(str, "Mutable", nonMutableStr);
         str = nonMutableStr;
      }
   }

   return match(str, typeName, outStr);
}

bool IAMInference::match(StringRef str, StringRef toMatch, NameBuffer &outStr) {
   // TODO: let options dictate fuzzy matching...

   auto strWords = camel_case::getWords(str);
   auto matchWords = camel_case::getWords(toMatch);

   auto strIter = strWords.begin();
   auto matchIter = matchWords.begin();

   // Match in order, but allowing interjected words
   while (strIter != strWords.end()) {
      if (matchIter == matchWords.end()) {
         // We matched them all!
         appendUniq(outStr, strIter.getRestOfStr());
         return true;
      }
      if (*strIter == *matchIter) {
         // It's a match!
         ++strIter;
         ++matchIter;
         continue;
      }
      // Move on to the next one
      appendUniq(outStr, *strIter);
      ++strIter;
   }

   return false;
}

// A loose type equality check that disregards all sugar, qualification, looks
// through pointers, etc.
static bool roughlyEqual(clang::QualType left, clang::QualType right) {
   auto leftPointee = left->getPointeeType();
   if (leftPointee != clang::QualType())
      left = leftPointee;
   auto rightPointee = right->getPointeeType();
   if (rightPointee != clang::QualType())
      right = rightPointee;
   return left->getUnqualifiedDesugaredType() ==
          right->getUnqualifiedDesugaredType();
}

static bool
isValidAsStaticProperty(const clang::FunctionDecl *getterDecl,
                        const clang::FunctionDecl *setterDecl = nullptr) {
   // Getter has none, setter has one arg
   if (getterDecl->getNumParams() != 0 ||
       (setterDecl && setterDecl->getNumParams() != 1)) {
      ++InvalidPropertyStaticNumParams;
      return false;
   }

   // Setter's arg type should be same as getter's return type
   auto getterTy = getterDecl->getReturnType();
   if (setterDecl &&
       !roughlyEqual(getterTy, setterDecl->getParamDecl(0)->getType())) {
      ++InvalidPropertyStaticGetterSetterType;
      return false;
   }

   return true;
}

static bool
isValidAsInstanceProperty(const clang::FunctionDecl *getterDecl,
                          const clang::FunctionDecl *setterDecl = nullptr) {
   // Instance property, look beyond self
   if (getterDecl->getNumParams() != 1 ||
       (setterDecl && setterDecl->getNumParams() != 2)) {
      ++InvalidPropertyInstanceNumParams;
      return false;
   }

   if (!setterDecl)
      return true;

   // Make sure they pair up
   auto getterTy = getterDecl->getReturnType();
   auto selfTy = getterDecl->getParamDecl(0)->getType();

   clang::QualType setterTy = {};
   auto setterParam0Ty = setterDecl->getParamDecl(0)->getType();
   auto setterParam1Ty = setterDecl->getParamDecl(1)->getType();

   if (roughlyEqual(setterParam0Ty, selfTy)) {
      setterTy = setterParam1Ty;
   } else if (roughlyEqual(setterParam1Ty, selfTy)) {
      setterTy = setterParam0Ty;
   } else {
      ++InvalidPropertyInstanceNoSelf;
      return false;
   }

   if (!roughlyEqual(setterTy, getterTy)) {
      ++InvalidPropertyInstanceGetterSetterType;
      return false;
   }

   return true;
}

bool IAMInference::validToImportAsProperty(
   const clang::FunctionDecl *originalDecl, StringRef propSpec,
   Optional<unsigned> selfIndex, const clang::FunctionDecl *&pairedAccessor) {
   bool isGet = propSpec == "Get";
   pairedAccessor = findPairedAccessor(originalDecl->getName(), propSpec);
   if (!pairedAccessor) {
      if (!isGet)
         return false;
      if (!selfIndex)
         return isValidAsStaticProperty(originalDecl);
      return isValidAsInstanceProperty(originalDecl);
   }

   auto getterDecl = isGet ? originalDecl : pairedAccessor;
   auto setterDecl = isGet ? pairedAccessor : originalDecl;

   if (getTopModule(getterDecl, clangSema) !=
       getTopModule(setterDecl, clangSema)) {
      // We paired up decls from two different modules, so either we still infer
      // as a getter with no setter, or we cannot be a property
      if (isGet) {
         pairedAccessor = nullptr;
         setterDecl = nullptr;
      } else  {
         // This is set-only as far as we're concerned
         return false;
      }
   }

   if (!selfIndex)
      return isValidAsStaticProperty(getterDecl, setterDecl);

   return isValidAsInstanceProperty(getterDecl, setterDecl);
}

IAMResult IAMInference::inferVar(const clang::VarDecl *varDecl) {
   auto fail = [varDecl]() -> IAMResult {
      LLVM_DEBUG(llvm::dbgs() << "failed to infer variable: ");
      LLVM_DEBUG(varDecl->print(llvm::dbgs()));
      (void)varDecl;
      LLVM_DEBUG(llvm::dbgs() << "\n");
      ++FailInferVar;
      return {};
   };

   // Try to find a type to add this as a static property to
   StringRef workingName = varDecl->getName();
   if (workingName.empty())
      return fail();

   // Special pattern: constants of the form "kFooBarBaz", extend "FooBar" with
   // property "Baz"
   if (*camel_case::getWords(workingName).begin() == "k")
      workingName = workingName.drop_front(1);

   NameBuffer remainingName;
   if (auto effectiveDC = findTypeAndMatch(workingName, remainingName))

      return importAsStaticProperty(remainingName, effectiveDC);

   return fail();
}

IAMResult IAMInference::infer(const clang::NamedDecl *clangDecl) {
   if (clangDecl->getName().startswith("_")) {
      ++SkipLeadingUnderscore;
      return {};
   }

   // Try to infer a member variable
   if (auto varDecl = dyn_cast<clang::VarDecl>(clangDecl))
      return inferVar(varDecl);

   // Try to infer a member function
   auto funcDecl = dyn_cast<clang::FunctionDecl>(clangDecl);
   if (!funcDecl) {
      // TODO: Do we want to collects stats here? Should it be assert?
      return {};
   }

   auto fail = [funcDecl]() -> IAMResult {
      LLVM_DEBUG(llvm::dbgs() << "failed to infer function: ");
      LLVM_DEBUG(funcDecl->print(llvm::dbgs()));
      (void)funcDecl;
      LLVM_DEBUG(llvm::dbgs() << "\n");
      ++FailInferFunction;
      return {};
   };

   // Can't really import variadics well
   if (funcDecl->isVariadic())
      return fail();

   // FIXME: drop "Mutable"...

   StringRef workingName = funcDecl->getName();
   auto retTy = funcDecl->getReturnType();
   unsigned numParams = funcDecl->getNumParams();

   // 0) Special cases are specially handled
   //
   StringRef getTypeID = "GetTypeID";
   StringRef cfSpecials[] = {"Release", "Retain", "Autorelease"};
   // *GetTypeID
   if (numParams == 0 && workingName.endswith(getTypeID)) {
      NameBuffer remainingName;
      if (auto effectiveDC = findTypeAndMatch(
         workingName.drop_back(getTypeID.size()), remainingName)) {
         // We shouldn't have anything else left in our name for typeID
         if (remainingName.empty()) {

            return importAsTypeID(retTy, effectiveDC);
         }
      }
      // *Release/*Retain/*Autorelease
   } else if (numParams == 1 &&
              std::any_of(std::begin(cfSpecials), std::end(cfSpecials),
                          [workingName](StringRef suffix) {
                             return workingName.endswith(suffix);
                          })) {
      if (auto type =
         funcDecl->getParamDecl(0)->getType()->getAs<clang::TypedefType>()) {
         if (CFPointeeInfo::classifyTypedef(type->getDecl())) {
            ++SkipCFMemoryManagement;
            return {};
         }
      }
   }

   // 1) If we find an init specifier and our name matches the return type, we
   //    import as some kind of constructor
   //
   if (!retTy->isVoidType()) {
      NameBuffer remainingName;
      if (matchTypeName(workingName, retTy, remainingName))
         for (auto initSpec : InitSpecifiers)
            if (hasWord(remainingName, initSpec))
               if (auto effectiveDC = getEffectiveDC(retTy))
                  return importAsConstructor(
                     remainingName, initSpec,
                     {funcDecl->param_begin(), funcDecl->param_end()}, effectiveDC);
   }

   // 2) If we find a likely self reference in the parameters, make an instance
   //    member (method or property)
   //
   SmallVector<const clang::ParmVarDecl *, 8> nonSelfParams;
   unsigned selfIdx = 0;
   for (auto paramI = funcDecl->param_begin(), paramE = funcDecl->param_end();
        paramI != paramE; ++paramI, ++selfIdx) {
      auto param = *paramI;
      NameBuffer remainingName;
      if (matchTypeName(workingName, param->getType(), remainingName)) {
         auto effectiveDC = getEffectiveDC(param->getType());
         if (!effectiveDC)
            continue;
         nonSelfParams.append(funcDecl->param_begin(), paramI);
         nonSelfParams.append(++paramI, paramE);
         // See if it's a property
         for (auto propSpec : PropertySpecifiers) {
            NameBuffer propName;
            if (match(remainingName, propSpec, propName)) {
               const clang::FunctionDecl *pairedAccessor;
               if (validToImportAsProperty(funcDecl, propSpec, selfIdx,
                                           pairedAccessor))
                  return importAsInstanceProperty(propName, propSpec, selfIdx,
                                                  nonSelfParams, pairedAccessor,
                                                  effectiveDC);
            }
         }

         return importAsInstanceMethod(remainingName, selfIdx, nonSelfParams,
                                       effectiveDC);
      }
   }

   // No self, must be static
   nonSelfParams = {funcDecl->param_begin(), funcDecl->param_end()};

   // 3) Finally, try to find a class to put this on as a static function
   NameBuffer remainingName;
   if (auto effectiveDC = findTypeAndMatch(workingName, remainingName)) {
      ArrayRef<const clang::ParmVarDecl *> params = {funcDecl->param_begin(),
                                                     funcDecl->param_end()};
      // See if it's a property
      for (auto propSpec : PropertySpecifiers) {
         NameBuffer propName;
         if (match(remainingName, propSpec, propName)) {
            const clang::FunctionDecl *pairedAccessor;
            if (validToImportAsProperty(funcDecl, propSpec, None, pairedAccessor))
               return importAsStaticProperty(propName, propSpec, nonSelfParams,
                                             pairedAccessor, effectiveDC);
         }
      }
      StringRef methodName =
         remainingName == "" ? workingName : StringRef(remainingName);
      return importAsStaticMethod(methodName, nonSelfParams, effectiveDC);
   }

   return fail();
}

template <typename DeclType>
DeclType *IAMInference::clangLookup(StringRef name,
                                    clang::Sema::LookupNameKind kind) {
   clang::IdentifierInfo *nameII = &clangSema.getASTContext().Idents.get(name);
   clang::LookupResult lookupResult(clangSema, clang::DeclarationName(nameII),
                                    clang::SourceLocation(), kind);
   if (!clangSema.LookupName(lookupResult, clangSema.TUScope))
      return nullptr;
   auto res = lookupResult.getAsSingle<DeclType>();
   if (!res)
      return nullptr;
   return res->getCanonicalDecl();
}

IAMResult IAMResult::infer(AstContext &ctx, clang::Sema &clangSema,
                           const clang::NamedDecl *decl, IAMOptions opts) {
   IAMInference inference(ctx, clangSema, opts);
   return inference.infer(decl);
}
